﻿using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.AutoMapper;
using MyCompanyName.AbpZeroTemplate.Authorization.Dto;
using MyCompanyName.AbpZeroTemplate.Authorization.Roles.Dto;
using MyCompanyName.AbpZeroTemplate.Prm.Authorization;
using System;
using Abp.Domain.Repositories;
using MyCompanyName.AbpZeroTemplate.Prm.Admin;
using System.Dynamic;

namespace MyCompanyName.AbpZeroTemplate.Authorization.Roles
{
    /// <summary>
    /// Application service that is used by 'role management' page.
    /// </summary>
    //[AbpAuthorize(AppPermissions.Pages_Administration_Roles)]
    public class RoleAppService : AbpZeroTemplateAppServiceBase, IRoleAppService
    {
        private readonly RoleManager _roleManager;

        private readonly IRepository<SystemRoleFunctionActions, int> systemRoleFunctionActionsRepository;
        private readonly IRepository<SystemRoleDocmentActions, int> systemRoleDocmentActionsRepository;
        private readonly IRepository<DocumentType, int> documentTypeRepository;

        public RoleAppService(RoleManager roleManager,
            IRepository<SystemRoleFunctionActions, int> _systemRoleFunctionActionsRepository,
            IRepository<SystemRoleDocmentActions, int> _systemRoleDocmentActionsRepository,
            IRepository<DocumentType, int> _documentTypeRepository)
        {
            systemRoleFunctionActionsRepository = _systemRoleFunctionActionsRepository;
            systemRoleDocmentActionsRepository = _systemRoleDocmentActionsRepository;
            documentTypeRepository = _documentTypeRepository;
            _roleManager = roleManager;
        }

        public async Task<ListResultOutput<RoleListDto>> GetRoles()
        {
            var roles = await _roleManager.Roles.ToListAsync();
            return new ListResultOutput<RoleListDto>(roles.MapTo<List<RoleListDto>>());
        }

        // [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Create, AppPermissions.Pages_Administration_Roles_Edit)]
        public async Task<GetRoleForEditOutput> GetRoleForEdit(NullableIdInput input)
        {
            var permissions = PermissionManager.GetAllPermissions();
            var grantedPermissions = new Permission[0];
            RoleEditDto roleEditDto;

            if (input.Id.HasValue) //Editing existing role?
            {
                var role = await _roleManager.GetRoleByIdAsync(input.Id.Value);
                grantedPermissions = (await _roleManager.GetGrantedPermissionsAsync(role)).ToArray();
                roleEditDto = role.MapTo<RoleEditDto>();
            }
            else
            {
                roleEditDto = new RoleEditDto();
            }

            return new GetRoleForEditOutput
                   {
                       Role = roleEditDto,
                       Permissions = permissions.MapTo<List<FlatPermissionDto>>().OrderBy(p => p.DisplayName).ToList(),
                       GrantedPermissionNames = grantedPermissions.Select(p => p.Name).ToList()
                   };
        }

        public async Task<List<Users.Dto.GetPermissionCheckTree>> GetRolePermission(int roleId)
        {
            var role = await _roleManager.GetRoleByIdAsync(roleId);
            var permissions = PermissionManager.GetAllPermissions();
            var grantedPermissions = await _roleManager.GetGrantedPermissionsAsync(role);
            Dictionary<string, Users.Dto.GetPermissionCheckTree> res = new Dictionary<string, Users.Dto.GetPermissionCheckTree>();

            foreach (var item in permissions)
            {
                var per = new Users.Dto.GetPermissionCheckTree()
                {
                    leaf = false,
                    children = new List<Users.Dto.GetPermissionCheckTree>(),
                    @checked = item.IsGrantedByDefault || grantedPermissions.Select(x => x.Name).Any(m => m == item.Name),
                    parentId = item.Parent == null ? "0" : item.Parent.Name,
                    text = item.Name
                };
                res.Add(item.Name, per);
            }

            foreach (var item in res.Values)
            {
                if (res.ContainsKey(item.parentId.ToString()))
                {
                    res[item.parentId.ToString()].children.Add(item);
                }
            }
            var results = res.Values.Where(x => x.parentId == "0").ToList();
            return results;
        }

        public async Task CreateOrUpdateRole(CreateOrUpdateRoleInput input)
        {
            if (input.Role.Id.HasValue)
            {
                await UpdateRoleAsync(input);
            }
            else
            {
                await CreateRoleAsync(input);
            }
        }

        //[AbpAuthorize(AppPermissions.Pages_Administration_Roles_Delete)]
        public async Task DeleteRole(EntityRequestInput input)
        {
            var role = await _roleManager.GetRoleByIdAsync(input.Id);
            CheckErrors(await _roleManager.DeleteAsync(role));
        }

        //[AbpAuthorize(AppPermissions.Pages_Administration_Roles_Edit)]
        protected virtual async Task UpdateRoleAsync(CreateOrUpdateRoleInput input)
        {
            Debug.Assert(input.Role.Id != null, "input.Role.Id should be set.");

            var role = await _roleManager.GetRoleByIdAsync(input.Role.Id.Value);
            role.DisplayName = input.Role.DisplayName;
            role.IsDefault = input.Role.IsDefault;

            await UpdateGrantedPermissionsAsync(role, input.GrantedPermissionNames);
        }

        // [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Create)]
        protected virtual async Task CreateRoleAsync(CreateOrUpdateRoleInput input)
        {
            var role = new Role(AbpSession.TenantId, input.Role.DisplayName) { IsDefault = input.Role.IsDefault };
            CheckErrors(await _roleManager.CreateAsync(role));
            await CurrentUnitOfWork.SaveChangesAsync(); //It's done to get Id of the role.
            await UpdateGrantedPermissionsAsync(role, input.GrantedPermissionNames);
        }

        private async Task UpdateGrantedPermissionsAsync(Role role, List<string> grantedPermissionNames)
        {
            var grantedPermissions = PermissionManager.GetPermissionsFromNamesByValidating(grantedPermissionNames);
            await _roleManager.SetGrantedPermissionsAsync(role, grantedPermissions);
        }


        public List<GetProjectPermissionOutPut> GetProjectPermission(int roleId)
        {
            List<GetProjectPermissionOutPut> result = new List<GetProjectPermissionOutPut>();

            foreach (var item in Enum.GetValues(typeof(ProjectFunctions)))
            {
                var fun = systemRoleFunctionActionsRepository.GetAll().Where(x => x.SystemRoleId == roleId && x.Permission == (ProjectFunctions)item).SingleOrDefault();
                var outPut = new GetProjectPermissionOutPut()
                {
                    Permission = item.ToString(),
                    HasAuth = false
                    //Scope = "Null"
                };
                if (fun != null)
                {
                    outPut.HasAuth = true;
                    //outPut.Scope = fun.ProjectScope.ToString();
                }
                result.Add(outPut);
            }
            return result;
        }


        public async Task SetProjectPermission(SetProjectPermissionInPut input)
        {
            await systemRoleFunctionActionsRepository.DeleteAsync(x => x.SystemRoleId == input.RoleId);
            foreach (var item in input.Permissions)
            {
                if (item.HasAuth)
                {
                    await systemRoleFunctionActionsRepository.InsertAsync(new SystemRoleFunctionActions()
                    {
                        SystemRoleId = input.RoleId,
                        Permission = (ProjectFunctions)Enum.Parse(typeof(ProjectFunctions), item.Permission)
                        //ProjectScope = (ActionScope)Enum.Parse(typeof(ActionScope), item.Scope)
                    });
                }
            }
        }



        public async Task<List<dynamic>> GetDocumentPermission(int roleId)
        {
            List<dynamic> result = new List<dynamic>();

            var types = await documentTypeRepository.GetAll().Where(x=>!x.IsDeleted&&x.IsEnabled).ToListAsync();

            foreach (var item in Enum.GetValues(typeof(DocmentFunctions)))
            {
                dynamic dy = new ExpandoObject();
                dy.DocumentAuth = item.ToString();
                foreach (var type in types)
                {
                    ((IDictionary<string, object>)dy).Add((type.Name + type.Id).ToLower(), HasDocumentAction(roleId, type.Id, (DocmentFunctions)item));
                }
                result.Add(dy);
            }
            return result;
        }



        public bool HasDocumentAction(int roleId, int typeId, DocmentFunctions action)
        {
            var hasAuth = systemRoleDocmentActionsRepository.GetAll().Where(x => x.SystemRoleId == roleId && x.DocTypeId == typeId && x.Permission == action).Any();
            return hasAuth;
        }







        public async Task SetDocumentPermission(SetDocumentPermissionInPut input)
        {
            systemRoleDocmentActionsRepository.Delete(x => x.SystemRoleId == input.RoleId);
            var types = await documentTypeRepository.GetAll().ToListAsync();
            foreach (var item in input.records)
            {
                var permission = (DocmentFunctions)Enum.Parse(typeof(DocmentFunctions), item["documentAuth"].Value);
                foreach (var r in types)
                {
                    var t = item[(r.Name + r.Id).ToLower()];
                    if (t != null && t.Value)
                    {
                        systemRoleDocmentActionsRepository.Insert(new SystemRoleDocmentActions()
                        {
                            Permission = permission,
                            SystemRoleId = input.RoleId,
                            DocTypeId = r.Id
                        });
                    }
                }
            }
        }


        public async Task<List<RoleListDto>> GetAll()
        {
            var roles = await _roleManager.Roles.OrderBy(x => x.Name).ToListAsync();
            var users = UserManager.Users.AsEnumerable().ToList();
            List<RoleListDto> result = new List<RoleListDto>();
            foreach (var item in roles)
            {
                var userNames = users.Where(x => x.Roles.Any(y => y.RoleId == item.Id)).OrderBy(x => x.UserName).Select(x => x.Name).ToList();
                result.Add(new RoleListDto()
                {
                    UserNames = string.Join(",", userNames),
                    DisplayName = item.DisplayName,
                    Name = item.Name,
                    CreationTime = item.CreationTime,
                    Id = item.Id
                });
            }

            return result;
        }


        public async Task SetRolePermissions(UpdateRolePermissionsInput input)
        {
            var role = await _roleManager.GetRoleByIdAsync((int)input.Id);
            await UpdateGrantedPermissionsAsync(role, input.GrantedPermissionNames);
        }


        public async Task EditRole(List<KeyValuePair<int, string>> input)
        {
            foreach (var item in input)
            {
                var role = await _roleManager.GetRoleByIdAsync(item.Key);
                role.DisplayName = item.Value;
                role.Name = item.Value;
            }
        }


        public async Task CreateRole(CreateRoleInPut input)
        {
            await _roleManager.CreateAsync(new Role() { DisplayName = input.DisplayName, Name = input.DisplayName, IsStatic = false });
        }


        public List<GetUserOutPut> GetUsers(int roleId)
        {
            var users = UserManager.Users.AsEnumerable().ToList();
            List<GetUserOutPut> result = new List<GetUserOutPut>();
            var list = users.Where(x => x.Roles.Any(y => y.RoleId == roleId)).OrderBy(x => x.UserName);
            foreach (var item in list)
            {
                result.Add(new GetUserOutPut()
                {
                    UserId = item.Id,
                    Name = item.Name,
                    UserName = item.UserName
                });
            }
            return result;
        }
    }
}
